library(tidyverse)         # for graphing and data cleaning
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.5     v purrr   0.3.4
## v tibble  3.1.3     v dplyr   1.0.7
## v tidyr   1.1.3     v stringr 1.4.0
## v readr   2.0.1     v forcats 0.5.1
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(tidymodels)        # for modeling
## Registered S3 method overwritten by 'tune':
##   method                   from   
##   required_pkgs.model_spec parsnip
## -- Attaching packages -------------------------------------- tidymodels 0.1.3 --
## v broom        0.7.9      v rsample      0.1.0 
## v dials        0.0.9      v tune         0.1.6 
## v infer        1.0.0      v workflows    0.2.3 
## v modeldata    0.1.1      v workflowsets 0.1.0 
## v parsnip      0.1.7      v yardstick    0.0.8 
## v recipes      0.1.16
## -- Conflicts ----------------------------------------- tidymodels_conflicts() --
## x scales::discard() masks purrr::discard()
## x dplyr::filter()   masks stats::filter()
## x recipes::fixed()  masks stringr::fixed()
## x dplyr::lag()      masks stats::lag()
## x yardstick::spec() masks readr::spec()
## x recipes::step()   masks stats::step()
## * Use tidymodels_prefer() to resolve common conflicts.
library(naniar)            # for analyzing missing values
library(vip)               # for variable importance plots
## 
## Attaching package: 'vip'
## The following object is masked from 'package:utils':
## 
##     vi
theme_set(theme_minimal()) # Lisa's favorite theme
hotels <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-02-11/hotels.csv')
## Rows: 119390 Columns: 32
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr  (13): hotel, arrival_date_month, meal, country, market_segment, distrib...
## dbl  (18): is_canceled, lead_time, arrival_date_year, arrival_date_week_numb...
## date  (1): reservation_status_date
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.

When you finish the assignment, remove the # from the options chunk at the top, so that messages and warnings aren’t printed. If you are getting errors in your code, add error = TRUE so that the file knits. I would recommend not removing the # until you are completely finished.

Put it on GitHub!

GitHub repo

Machine Learning review and intro to tidymodels

  1. Read about the hotel booking data, hotels, on the Tidy Tuesday page it came from. There is also a link to an article from the original authors. The outcome we will be predicting is called is_canceled.

babies, is_repeated_guest, previous_cancellations, and deposit_type might be predictive variables. With babies, it is possible to have a lot of emergencies going on, and thus plans could be changing constantly. Being a repeated guest may lead to a lower likelihood of cancellation because they have already known about the hotel. previous_cancellations is a good predictive to tell the general habit of the guest. With the deposit, it would be less likely for guests to cancel in order not to lose their money.

Some of the variables were engineered from other variables from different database tables.

We will be able to know which are the most important variables that can predictive is_cancaled and how they affect it.

  1. Create some exploratory plots or table summaries of the variables in the dataset. Be sure to also examine missing values or other interesting values. You may want to adjust the fig.width and fig.height in the code chunk options.
hotels %>% 
  select(where(is.numeric)) %>% 
  pivot_longer(cols = everything(),
               names_to = "variable", 
               values_to = "value") %>% 
  ggplot(aes(x = value)) +
  geom_histogram(bins = 30) +
  facet_wrap(vars(variable), 
             scales = "free",
             nrow = 5)
## Warning: Removed 4 rows containing non-finite values (stat_bin).

hotels %>%
  mutate(across(where(is.character), as.factor)) %>% 
  select(where(is.factor)) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "value") %>%
  ggplot(aes(x = value)) +
  geom_bar() +
  facet_wrap(vars(variable),
             scales = "free",
             nrow = 4)

hotels %>% 
  add_n_miss() %>% 
  count(n_miss_all)
  1. First, we will do a couple things to get the data ready.
hotels_mod <- hotels %>% 
  mutate(is_canceled = as.factor(is_canceled)) %>% 
  mutate(across(where(is.character), as.factor)) %>% 
  select(-arrival_date_year,
         -reservation_status,
         -reservation_status_date) %>% 
  add_n_miss() %>% 
  filter(n_miss_all == 0) %>% 
  select(-n_miss_all)

set.seed(494)

hotel_split <- initial_split(hotels_mod, prop = .5, strata = is_canceled)
hotel_training <- training(hotel_split)
hotel_testing <- testing(hotel_split)
  1. In this next step, we are going to do the pre-processing. Usually, I won’t tell you exactly what to do here, but for your first exercise, I’ll tell you the steps.
hotel_recipe <- recipe(is_canceled ~ ., data = hotel_training) %>% 
  step_mutate(has_child = as.factor(as.numeric(children > 0)),
              has_baby = as.factor(as.numeric(babies > 0)),
              has_precancel = as.factor(as.numeric(previous_cancellations > 0)),
              has_agent = as.factor(as.numeric(agent == 'NULL')),
              has_company = as.factor(as.numeric(company == 'NULL')),
              country = fct_lump_n(country, 5)) %>% 
  step_rm(children,
          babies,
          previous_cancellations,
          agent,
          company) %>% 
  step_normalize(all_predictors(), 
                 -all_nominal()) %>% 
  step_dummy(all_nominal(), 
             -all_outcomes())
hotel_recipe %>% 
  prep(hotel_training) %>%
  juice()
  1. In this step we will set up a LASSO model and workflow.

LASSO shrinks some of the coefficients of variables to 0 so that we don’t have too many predictors in the model. In this case, we have almost 30 predictors, which is a lot, so it would be great to get rid of some of them.

hotel_lasso_mod <- 
  logistic_reg(mixture = 1) %>% 
  set_engine("glmnet") %>% 
  set_args(penalty = tune()) %>% 
  set_mode("classification")
hotel_lasso_wf <- 
  workflow() %>% 
  add_recipe(hotel_recipe) %>% 
  add_model(hotel_lasso_mod)

hotel_lasso_wf
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: logistic_reg()
## 
## -- Preprocessor ----------------------------------------------------------------
## 4 Recipe Steps
## 
## * step_mutate()
## * step_rm()
## * step_normalize()
## * step_dummy()
## 
## -- Model -----------------------------------------------------------------------
## Logistic Regression Model Specification (classification)
## 
## Main Arguments:
##   penalty = tune()
##   mixture = 1
## 
## Computational engine: glmnet
  1. In this step, we’ll tune the model and fit the model using the best tuning parameter to the entire training dataset.
set.seed(494) # for reproducibility

hotel_cv <- vfold_cv(hotel_training, v = 5)
penalty_grid <- grid_regular(penalty(),
                             levels = 10)
penalty_grid 
hotel_lasso_tune <- 
  hotel_lasso_wf %>% 
  tune_grid(
    resamples = hotel_cv,
    grid = penalty_grid
    )

hotel_lasso_tune
hotel_lasso_tune %>% 
  collect_metrics() %>% 
  filter(.metric == "accuracy")
hotel_lasso_tune %>% 
  collect_metrics() %>% 
  filter(.metric == "accuracy") %>% 
  ggplot(aes(x = penalty, y = mean)) +
  geom_point() +
  geom_line() +
  scale_x_log10(breaks = scales::trans_breaks("log10", function(x) 10^x),
                labels = scales::trans_format("log10",scales::math_format(10^.x))) +
  labs(x = "penalty", y = "accuracy")

best_param <- hotel_lasso_tune %>% 
  select_best(metric = "accuracy")
hotel_lasso_final_wf <- hotel_lasso_wf %>% 
  finalize_workflow(best_param)

hotel_lasso_final_mod <- hotel_lasso_final_wf %>% 
  fit(data = hotel_training)

hotel_lasso_final_mod %>% 
  pull_workflow_fit() %>% 
  tidy() 
## Warning: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
## Please use `extract_fit_parsnip()` instead.

arrival_date_month_September, market_segment_Groups, market_segment_Undefined, distribution_channel_Undefined, and customer_type_Group have coefficients of 0.

  1. Now that we have a model, let’s evaluate it a bit more. All we have looked at so far is the cross-validated accuracy from the previous step.
hotel_lasso_final_mod %>% 
  pull_workflow_fit() %>% 
  vip()
## Warning: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
## Please use `extract_fit_parsnip()` instead.

reserved_room_type_P is the most important variable. I’m not very surprised because reserved room type may tell approximately how many guests there are and what their purpose of the reservation is, which could determine the likihood of cancelling the event.

hotel_lasso_test <- hotel_lasso_final_wf %>% 
  last_fit(hotel_split)

hotel_lasso_test %>% 
  collect_metrics()
preds <- collect_predictions(hotel_lasso_test)

conf_mat(preds, is_canceled, .pred_class)
##           Truth
## Prediction     0     1
##          0 34179  7777
##          1  3404 14333

The test metric is slightly lower than the cross-validated one, but they are pretty close to each other.

preds %>% 
  ggplot(aes(x = .pred_1, fill = is_canceled)) +
  geom_density(alpha = 0.5, color = NA)

  1. What would this graph look like for a model with an accuracy that was close to 1?

.pred_1 equals 1 for all is_canceled = 1 and .pred_1 equals 0 for all is_canceled = 0?

  1. Our predictions are classified as canceled if their predicted probability of canceling is greater than .5. If we wanted to have a high true positive rate, should we make the cutoff for predicted as canceled higher or lower than .5?

Lower than 0.5.

  1. What happens to the true negative rate if we try to get a higher true positive rate?

It will be lower.

  1. Let’s say that this model is going to be applied to bookings 14 days in advance of their arrival at each hotel, and someone who works for the hotel will make a phone call to the person who made the booking. During this phone call, they will try to assure that the person will be keeping their reservation or that they will be canceling in which case they can do that now and still have time to fill the room. How should the hotel go about deciding who to call? How could they measure whether it was worth the effort to do the calling? Can you think of another way they might use the model?

The hotel should call guests with reserved room type P because it is the variable that influences the outcome the most. To measure whether it was worth the effort, it could be helpful to look at other important variables such as deposit type, previous cancellation, and assigned room type which make guests more likely to cancel. They can also use the model to improve their reservation system. For example, if they make all deposits non-refundable to lower the likelihood of cancellation.

  1. How might you go about questioning and evaluating the model in terms of fairness? Are there any questions you would like to ask of the people who collected the data?

I would like to learn the proportion of young and old people and the proportion of different gender/race groups to check if the data underrepresent any group. I would also like to ask what they think are the most important variables influencing cancellation because their primary knowledge may lead to a biased collection of data.

Bias and Fairness

Read Chapter 1: The Power Chapter of Data Feminism by Catherine D’Ignazio and Lauren Klein. Write a 4-6 sentence paragraph reflecting on this chapter. As you reflect, you might consider responding to these specific questions. We will also have a discussion about these questions in class on Thursday.

  • At the end of the “Matrix of Domination” section, they encourage us to “ask uncomfortable questions: who is doing the work of data science (and who is not)? Whose goals are prioritized in data science (and whose are not)? And who benefits from data science (and who is either overlooked or actively harmed)?” In general, how would you answer these questions? And why are they important?

I believe the field of data science is still predominated by men, which is also the group that is prioritized. Because of higher socioeconomic status, men could be the primary target of high-tech products and can benefit from these technologies. On the other hand, all other genders could be overlooked or actively harmed. It is important to think about these questions because without thinking deeply about them, people might not even realize the problem. And realizing a problem is the first step to solve it. If the problem is not noticed and corrected in time, the data collected leads to a biased product which then leads to a more biased collection of data. Such a cycle can go on and on and further harm the minoritized groups.

  • Can you think of any examples of missing datasets, like those described in the “Data Science for Whom?” section? Or was there an example there that surprised you?

I was surprised that Mobility for older adults with physical disabilities or cognitive impairments is missing. Physical disabilities and cognitive impairments, I believe, are symptoms a large group of elderly people experiences. With increasing attention to public health, it is surprising that there is no dataset covering this area. This could also indicate the older adults are an overlooked and minoritized group, which makes no sense because they are the group more likely to experience the most health-related issues.

  • How did the examples in the “Data Science with Whose Interests and Goals?” section make you feel? What responsibility do companies have to prevent these things from occurring? Who is to blame?

I feel sad because I always see data as something mostly objective and as a tool to provide evidence-based explanations/conclusions. These examples show that I am wrong and I am upset to see that data is not bringing the good as it supposes to be. Companies should be extra careful about the source of their data, how their data was collected, and whether their data under or overrepresent any groups. Anyone in the process could potentially do something incorrectly, but more important is to let people realize potential problems associated with the usage of data.

LS0tDQp0aXRsZTogJ0Fzc2lnbm1lbnQgIzInDQphdXRob3I6ICdZdW55YW5nIFpob25nJw0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQoja25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQ0KYGBgDQoNCmBgYHtyIGxpYnJhcmllc30NCmxpYnJhcnkodGlkeXZlcnNlKSAgICAgICAgICMgZm9yIGdyYXBoaW5nIGFuZCBkYXRhIGNsZWFuaW5nDQpsaWJyYXJ5KHRpZHltb2RlbHMpICAgICAgICAjIGZvciBtb2RlbGluZw0KbGlicmFyeShuYW5pYXIpICAgICAgICAgICAgIyBmb3IgYW5hbHl6aW5nIG1pc3NpbmcgdmFsdWVzDQpsaWJyYXJ5KHZpcCkgICAgICAgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzDQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjIExpc2EncyBmYXZvcml0ZSB0aGVtZQ0KYGBgDQoNCmBgYHtyIGRhdGF9DQpob3RlbHMgPC0gcmVhZHI6OnJlYWRfY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wMi0xMS9ob3RlbHMuY3N2JykNCmBgYA0KDQpXaGVuIHlvdSBmaW5pc2ggdGhlIGFzc2lnbm1lbnQsIHJlbW92ZSB0aGUgYCNgIGZyb20gdGhlIG9wdGlvbnMgY2h1bmsgYXQgdGhlIHRvcCwgc28gdGhhdCBtZXNzYWdlcyBhbmQgd2FybmluZ3MgYXJlbid0IHByaW50ZWQuIElmIHlvdSBhcmUgZ2V0dGluZyBlcnJvcnMgaW4geW91ciBjb2RlLCBhZGQgYGVycm9yID0gVFJVRWAgc28gdGhhdCB0aGUgZmlsZSBrbml0cy4gSSB3b3VsZCByZWNvbW1lbmQgbm90IHJlbW92aW5nIHRoZSBgI2AgdW50aWwgeW91IGFyZSBjb21wbGV0ZWx5IGZpbmlzaGVkLg0KDQojIyBQdXQgaXQgb24gR2l0SHViISAgICAgICAgDQoNCltHaXRIdWIgcmVwb10oaHR0cHM6Ly9naXRodWIuY29tL3l6aG9uZzA2MjAvU1RBVC00NTYtQXNzaWdubWVudC0yKQ0KDQojIyBNYWNoaW5lIExlYXJuaW5nIHJldmlldyBhbmQgaW50cm8gdG8gYHRpZHltb2RlbHNgDQoNCjEuIFJlYWQgYWJvdXQgdGhlIGhvdGVsIGJvb2tpbmcgZGF0YSwgYGhvdGVsc2AsIG9uIHRoZSBbVGlkeSBUdWVzZGF5IHBhZ2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvYmxvYi9tYXN0ZXIvZGF0YS8yMDIwLzIwMjAtMDItMTEvcmVhZG1lLm1kKSBpdCBjYW1lIGZyb20uIFRoZXJlIGlzIGFsc28gYSBsaW5rIHRvIGFuIGFydGljbGUgZnJvbSB0aGUgb3JpZ2luYWwgYXV0aG9ycy4gVGhlIG91dGNvbWUgd2Ugd2lsbCBiZSBwcmVkaWN0aW5nIGlzIGNhbGxlZCBgaXNfY2FuY2VsZWRgLiANCiAgDQo+IGJhYmllcywgaXNfcmVwZWF0ZWRfZ3Vlc3QsIHByZXZpb3VzX2NhbmNlbGxhdGlvbnMsIGFuZCBkZXBvc2l0X3R5cGUgbWlnaHQgYmUgcHJlZGljdGl2ZSB2YXJpYWJsZXMuIFdpdGggYmFiaWVzLCBpdCBpcyBwb3NzaWJsZSB0byBoYXZlIGEgbG90IG9mIGVtZXJnZW5jaWVzIGdvaW5nIG9uLCBhbmQgdGh1cyBwbGFucyBjb3VsZCBiZSBjaGFuZ2luZyBjb25zdGFudGx5LiBCZWluZyBhIHJlcGVhdGVkIGd1ZXN0IG1heSBsZWFkIHRvIGEgbG93ZXIgbGlrZWxpaG9vZCBvZiBjYW5jZWxsYXRpb24gYmVjYXVzZSB0aGV5IGhhdmUgYWxyZWFkeSBrbm93biBhYm91dCB0aGUgaG90ZWwuIHByZXZpb3VzX2NhbmNlbGxhdGlvbnMgaXMgYSBnb29kIHByZWRpY3RpdmUgdG8gdGVsbCB0aGUgZ2VuZXJhbCBoYWJpdCBvZiB0aGUgZ3Vlc3QuIFdpdGggdGhlIGRlcG9zaXQsIGl0IHdvdWxkIGJlIGxlc3MgbGlrZWx5IGZvciBndWVzdHMgdG8gY2FuY2VsIGluIG9yZGVyIG5vdCB0byBsb3NlIHRoZWlyIG1vbmV5Lg0KDQo+IFNvbWUgb2YgdGhlIHZhcmlhYmxlcyB3ZXJlIGVuZ2luZWVyZWQgZnJvbSBvdGhlciB2YXJpYWJsZXMgZnJvbSBkaWZmZXJlbnQgZGF0YWJhc2UgdGFibGVzLiANCg0KPiBXZSB3aWxsIGJlIGFibGUgdG8ga25vdyB3aGljaCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyB0aGF0IGNhbiBwcmVkaWN0aXZlIGlzX2NhbmNhbGVkIGFuZCBob3cgdGhleSBhZmZlY3QgaXQuDQoNCjIuIENyZWF0ZSBzb21lIGV4cGxvcmF0b3J5IHBsb3RzIG9yIHRhYmxlIHN1bW1hcmllcyBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0LiBCZSBzdXJlIHRvIGFsc28gZXhhbWluZSBtaXNzaW5nIHZhbHVlcyBvciBvdGhlciBpbnRlcmVzdGluZyB2YWx1ZXMuIFlvdSBtYXkgd2FudCB0byBhZGp1c3QgdGhlIGBmaWcud2lkdGhgIGFuZCBgZmlnLmhlaWdodGAgaW4gdGhlIGNvZGUgY2h1bmsgb3B0aW9ucy4gIA0KDQpgYGB7cn0NCmhvdGVscyAlPiUgDQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzApICsNCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwgDQogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLA0KICAgICAgICAgICAgIG5yb3cgPSA1KQ0KYGBgDQoNCmBgYHtyfQ0KaG90ZWxzICU+JQ0KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIGFzLmZhY3RvcikpICU+JSANCiAgc2VsZWN0KHdoZXJlKGlzLmZhY3RvcikpICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInZhcmlhYmxlIiwNCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZSIpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSkpICsNCiAgZ2VvbV9iYXIoKSArDQogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksDQogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLA0KICAgICAgICAgICAgIG5yb3cgPSA0KQ0KYGBgDQoNCmBgYHtyfQ0KaG90ZWxzICU+JSANCiAgYWRkX25fbWlzcygpICU+JSANCiAgY291bnQobl9taXNzX2FsbCkNCmBgYA0KDQozLiBGaXJzdCwgd2Ugd2lsbCBkbyBhIGNvdXBsZSB0aGluZ3MgdG8gZ2V0IHRoZSBkYXRhIHJlYWR5LiANCg0KYGBge3J9DQpob3RlbHNfbW9kIDwtIGhvdGVscyAlPiUgDQogIG11dGF0ZShpc19jYW5jZWxlZCA9IGFzLmZhY3Rvcihpc19jYW5jZWxlZCkpICU+JSANCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCBhcy5mYWN0b3IpKSAlPiUgDQogIHNlbGVjdCgtYXJyaXZhbF9kYXRlX3llYXIsDQogICAgICAgICAtcmVzZXJ2YXRpb25fc3RhdHVzLA0KICAgICAgICAgLXJlc2VydmF0aW9uX3N0YXR1c19kYXRlKSAlPiUgDQogIGFkZF9uX21pc3MoKSAlPiUgDQogIGZpbHRlcihuX21pc3NfYWxsID09IDApICU+JSANCiAgc2VsZWN0KC1uX21pc3NfYWxsKQ0KDQpzZXQuc2VlZCg0OTQpDQoNCmhvdGVsX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoaG90ZWxzX21vZCwgcHJvcCA9IC41LCBzdHJhdGEgPSBpc19jYW5jZWxlZCkNCmhvdGVsX3RyYWluaW5nIDwtIHRyYWluaW5nKGhvdGVsX3NwbGl0KQ0KaG90ZWxfdGVzdGluZyA8LSB0ZXN0aW5nKGhvdGVsX3NwbGl0KQ0KYGBgDQoNCjQuIEluIHRoaXMgbmV4dCBzdGVwLCB3ZSBhcmUgZ29pbmcgdG8gZG8gdGhlIHByZS1wcm9jZXNzaW5nLiBVc3VhbGx5LCBJIHdvbid0IHRlbGwgeW91IGV4YWN0bHkgd2hhdCB0byBkbyBoZXJlLCBidXQgZm9yIHlvdXIgZmlyc3QgZXhlcmNpc2UsIEknbGwgdGVsbCB5b3UgdGhlIHN0ZXBzLiANCg0KYGBge3J9DQpob3RlbF9yZWNpcGUgPC0gcmVjaXBlKGlzX2NhbmNlbGVkIH4gLiwgZGF0YSA9IGhvdGVsX3RyYWluaW5nKSAlPiUgDQogIHN0ZXBfbXV0YXRlKGhhc19jaGlsZCA9IGFzLmZhY3Rvcihhcy5udW1lcmljKGNoaWxkcmVuID4gMCkpLA0KICAgICAgICAgICAgICBoYXNfYmFieSA9IGFzLmZhY3Rvcihhcy5udW1lcmljKGJhYmllcyA+IDApKSwNCiAgICAgICAgICAgICAgaGFzX3ByZWNhbmNlbCA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHByZXZpb3VzX2NhbmNlbGxhdGlvbnMgPiAwKSksDQogICAgICAgICAgICAgIGhhc19hZ2VudCA9IGFzLmZhY3Rvcihhcy5udW1lcmljKGFnZW50ID09ICdOVUxMJykpLA0KICAgICAgICAgICAgICBoYXNfY29tcGFueSA9IGFzLmZhY3Rvcihhcy5udW1lcmljKGNvbXBhbnkgPT0gJ05VTEwnKSksDQogICAgICAgICAgICAgIGNvdW50cnkgPSBmY3RfbHVtcF9uKGNvdW50cnksIDUpKSAlPiUgDQogIHN0ZXBfcm0oY2hpbGRyZW4sDQogICAgICAgICAgYmFiaWVzLA0KICAgICAgICAgIHByZXZpb3VzX2NhbmNlbGxhdGlvbnMsDQogICAgICAgICAgYWdlbnQsDQogICAgICAgICAgY29tcGFueSkgJT4lIA0KICBzdGVwX25vcm1hbGl6ZShhbGxfcHJlZGljdG9ycygpLCANCiAgICAgICAgICAgICAgICAgLWFsbF9ub21pbmFsKCkpICU+JSANCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCANCiAgICAgICAgICAgICAtYWxsX291dGNvbWVzKCkpDQpgYGANCg0KYGBge3J9DQpob3RlbF9yZWNpcGUgJT4lIA0KICBwcmVwKGhvdGVsX3RyYWluaW5nKSAlPiUNCiAganVpY2UoKQ0KYGBgDQoNCjUuIEluIHRoaXMgc3RlcCB3ZSB3aWxsIHNldCB1cCBhIExBU1NPIG1vZGVsIGFuZCB3b3JrZmxvdy4NCg0KPiBMQVNTTyBzaHJpbmtzIHNvbWUgb2YgdGhlIGNvZWZmaWNpZW50cyBvZiB2YXJpYWJsZXMgdG8gMCBzbyB0aGF0IHdlIGRvbid0IGhhdmUgdG9vIG1hbnkgcHJlZGljdG9ycyBpbiB0aGUgbW9kZWwuIEluIHRoaXMgY2FzZSwgd2UgaGF2ZSBhbG1vc3QgMzAgcHJlZGljdG9ycywgd2hpY2ggaXMgYSBsb3QsIHNvIGl0IHdvdWxkIGJlIGdyZWF0IHRvIGdldCByaWQgb2Ygc29tZSBvZiB0aGVtLg0KDQpgYGB7cn0NCmhvdGVsX2xhc3NvX21vZCA8LSANCiAgbG9naXN0aWNfcmVnKG1peHR1cmUgPSAxKSAlPiUgDQogIHNldF9lbmdpbmUoImdsbW5ldCIpICU+JSANCiAgc2V0X2FyZ3MocGVuYWx0eSA9IHR1bmUoKSkgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KYGBgDQoNCmBgYHtyfQ0KaG90ZWxfbGFzc29fd2YgPC0gDQogIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfcmVjaXBlKGhvdGVsX3JlY2lwZSkgJT4lIA0KICBhZGRfbW9kZWwoaG90ZWxfbGFzc29fbW9kKQ0KDQpob3RlbF9sYXNzb193Zg0KYGBgDQoNCjYuIEluIHRoaXMgc3RlcCwgd2UnbGwgdHVuZSB0aGUgbW9kZWwgYW5kIGZpdCB0aGUgbW9kZWwgdXNpbmcgdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlciB0byB0aGUgZW50aXJlIHRyYWluaW5nIGRhdGFzZXQuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNDk0KSAjIGZvciByZXByb2R1Y2liaWxpdHkNCg0KaG90ZWxfY3YgPC0gdmZvbGRfY3YoaG90ZWxfdHJhaW5pbmcsIHYgPSA1KQ0KYGBgDQoNCmBgYHtyfQ0KcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IDEwKQ0KcGVuYWx0eV9ncmlkIA0KYGBgDQoNCmBgYHtyfQ0KaG90ZWxfbGFzc29fdHVuZSA8LSANCiAgaG90ZWxfbGFzc29fd2YgJT4lIA0KICB0dW5lX2dyaWQoDQogICAgcmVzYW1wbGVzID0gaG90ZWxfY3YsDQogICAgZ3JpZCA9IHBlbmFsdHlfZ3JpZA0KICAgICkNCg0KaG90ZWxfbGFzc29fdHVuZQ0KYGBgDQoNCmBgYHtyfQ0KaG90ZWxfbGFzc29fdHVuZSAlPiUgDQogIGNvbGxlY3RfbWV0cmljcygpICU+JSANCiAgZmlsdGVyKC5tZXRyaWMgPT0gImFjY3VyYWN5IikNCg0KaG90ZWxfbGFzc29fdHVuZSAlPiUgDQogIGNvbGxlY3RfbWV0cmljcygpICU+JSANCiAgZmlsdGVyKC5tZXRyaWMgPT0gImFjY3VyYWN5IikgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBwZW5hbHR5LCB5ID0gbWVhbikpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBzY2FsZV94X2xvZzEwKGJyZWFrcyA9IHNjYWxlczo6dHJhbnNfYnJlYWtzKCJsb2cxMCIsIGZ1bmN0aW9uKHgpIDEwXngpLA0KICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpKSArDQogIGxhYnMoeCA9ICJwZW5hbHR5IiwgeSA9ICJhY2N1cmFjeSIpDQpgYGANCg0KYGBge3J9DQpiZXN0X3BhcmFtIDwtIGhvdGVsX2xhc3NvX3R1bmUgJT4lIA0KICBzZWxlY3RfYmVzdChtZXRyaWMgPSAiYWNjdXJhY3kiKQ0KYGBgDQoNCmBgYHtyfQ0KaG90ZWxfbGFzc29fZmluYWxfd2YgPC0gaG90ZWxfbGFzc29fd2YgJT4lIA0KICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3BhcmFtKQ0KDQpob3RlbF9sYXNzb19maW5hbF9tb2QgPC0gaG90ZWxfbGFzc29fZmluYWxfd2YgJT4lIA0KICBmaXQoZGF0YSA9IGhvdGVsX3RyYWluaW5nKQ0KDQpob3RlbF9sYXNzb19maW5hbF9tb2QgJT4lIA0KICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSANCiAgdGlkeSgpIA0KYGBgDQoNCj4gYXJyaXZhbF9kYXRlX21vbnRoX1NlcHRlbWJlciwgbWFya2V0X3NlZ21lbnRfR3JvdXBzLCBtYXJrZXRfc2VnbWVudF9VbmRlZmluZWQsIGRpc3RyaWJ1dGlvbl9jaGFubmVsX1VuZGVmaW5lZCwgIGFuZCBjdXN0b21lcl90eXBlX0dyb3VwIGhhdmUgY29lZmZpY2llbnRzIG9mIDAuDQoNCjcuIE5vdyB0aGF0IHdlIGhhdmUgYSBtb2RlbCwgbGV0J3MgZXZhbHVhdGUgaXQgYSBiaXQgbW9yZS4gQWxsIHdlIGhhdmUgbG9va2VkIGF0IHNvIGZhciBpcyB0aGUgY3Jvc3MtdmFsaWRhdGVkIGFjY3VyYWN5IGZyb20gdGhlIHByZXZpb3VzIHN0ZXAuIA0KDQpgYGB7cn0NCmhvdGVsX2xhc3NvX2ZpbmFsX21vZCAlPiUgDQogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIA0KICB2aXAoKQ0KYGBgDQoNCj4gcmVzZXJ2ZWRfcm9vbV90eXBlX1AgaXMgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlLiBJJ20gbm90IHZlcnkgc3VycHJpc2VkIGJlY2F1c2UgcmVzZXJ2ZWQgcm9vbSB0eXBlIG1heSB0ZWxsIGFwcHJveGltYXRlbHkgaG93IG1hbnkgZ3Vlc3RzIHRoZXJlIGFyZSBhbmQgd2hhdCB0aGVpciBwdXJwb3NlIG9mIHRoZSByZXNlcnZhdGlvbiBpcywgd2hpY2ggY291bGQgZGV0ZXJtaW5lIHRoZSBsaWtpaG9vZCBvZiBjYW5jZWxsaW5nIHRoZSBldmVudC4NCg0KYGBge3J9DQpob3RlbF9sYXNzb190ZXN0IDwtIGhvdGVsX2xhc3NvX2ZpbmFsX3dmICU+JSANCiAgbGFzdF9maXQoaG90ZWxfc3BsaXQpDQoNCmhvdGVsX2xhc3NvX3Rlc3QgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKQ0KDQpwcmVkcyA8LSBjb2xsZWN0X3ByZWRpY3Rpb25zKGhvdGVsX2xhc3NvX3Rlc3QpDQoNCmNvbmZfbWF0KHByZWRzLCBpc19jYW5jZWxlZCwgLnByZWRfY2xhc3MpDQpgYGANCg0KPiBUaGUgdGVzdCBtZXRyaWMgaXMgc2xpZ2h0bHkgbG93ZXIgdGhhbiB0aGUgY3Jvc3MtdmFsaWRhdGVkIG9uZSwgYnV0IHRoZXkgYXJlIHByZXR0eSBjbG9zZSB0byBlYWNoIG90aGVyLg0KDQpgYGB7cn0NCnByZWRzICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gLnByZWRfMSwgZmlsbCA9IGlzX2NhbmNlbGVkKSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUsIGNvbG9yID0gTkEpDQpgYGANCg0KYS4gV2hhdCB3b3VsZCB0aGlzIGdyYXBoIGxvb2sgbGlrZSBmb3IgYSBtb2RlbCB3aXRoIGFuIGFjY3VyYWN5IHRoYXQgd2FzIGNsb3NlIHRvIDE/ICANCg0KPiAucHJlZF8xIGVxdWFscyAxIGZvciBhbGwgaXNfY2FuY2VsZWQgPSAxIGFuZCAucHJlZF8xIGVxdWFscyAwIGZvciBhbGwgaXNfY2FuY2VsZWQgPSAwPw0KDQpiLiBPdXIgcHJlZGljdGlvbnMgYXJlIGNsYXNzaWZpZWQgYXMgY2FuY2VsZWQgaWYgdGhlaXIgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGNhbmNlbGluZyBpcyBncmVhdGVyIHRoYW4gLjUuIElmIHdlIHdhbnRlZCB0byBoYXZlIGEgaGlnaCB0cnVlIHBvc2l0aXZlIHJhdGUsIHNob3VsZCB3ZSBtYWtlIHRoZSBjdXRvZmYgZm9yIHByZWRpY3RlZCBhcyBjYW5jZWxlZCBoaWdoZXIgb3IgbG93ZXIgdGhhbiAuNT8NCg0KPiBMb3dlciB0aGFuIDAuNS4NCg0KYy4gV2hhdCBoYXBwZW5zIHRvIHRoZSB0cnVlIG5lZ2F0aXZlIHJhdGUgaWYgd2UgdHJ5IHRvIGdldCBhIGhpZ2hlciB0cnVlIHBvc2l0aXZlIHJhdGU/IA0KDQo+IEl0IHdpbGwgYmUgbG93ZXIuDQoNCjguIExldCdzIHNheSB0aGF0IHRoaXMgbW9kZWwgaXMgZ29pbmcgdG8gYmUgYXBwbGllZCB0byBib29raW5ncyAxNCBkYXlzIGluIGFkdmFuY2Ugb2YgdGhlaXIgYXJyaXZhbCBhdCBlYWNoIGhvdGVsLCBhbmQgc29tZW9uZSB3aG8gd29ya3MgZm9yIHRoZSBob3RlbCB3aWxsIG1ha2UgYSBwaG9uZSBjYWxsIHRvIHRoZSBwZXJzb24gd2hvIG1hZGUgdGhlIGJvb2tpbmcuIER1cmluZyB0aGlzIHBob25lIGNhbGwsIHRoZXkgd2lsbCB0cnkgdG8gYXNzdXJlIHRoYXQgdGhlIHBlcnNvbiB3aWxsIGJlIGtlZXBpbmcgdGhlaXIgcmVzZXJ2YXRpb24gb3IgdGhhdCB0aGV5IHdpbGwgYmUgY2FuY2VsaW5nIGluIHdoaWNoIGNhc2UgdGhleSBjYW4gZG8gdGhhdCBub3cgYW5kIHN0aWxsIGhhdmUgdGltZSB0byBmaWxsIHRoZSByb29tLiBIb3cgc2hvdWxkIHRoZSBob3RlbCBnbyBhYm91dCBkZWNpZGluZyB3aG8gdG8gY2FsbD8gSG93IGNvdWxkIHRoZXkgbWVhc3VyZSB3aGV0aGVyIGl0IHdhcyB3b3J0aCB0aGUgZWZmb3J0IHRvIGRvIHRoZSBjYWxsaW5nPyBDYW4geW91IHRoaW5rIG9mIGFub3RoZXIgd2F5IHRoZXkgbWlnaHQgdXNlIHRoZSBtb2RlbD8gDQoNCj4gVGhlIGhvdGVsIHNob3VsZCBjYWxsIGd1ZXN0cyB3aXRoIHJlc2VydmVkIHJvb20gdHlwZSBQIGJlY2F1c2UgaXQgaXMgdGhlIHZhcmlhYmxlIHRoYXQgaW5mbHVlbmNlcyB0aGUgb3V0Y29tZSB0aGUgbW9zdC4gVG8gbWVhc3VyZSB3aGV0aGVyIGl0IHdhcyB3b3J0aCB0aGUgZWZmb3J0LCBpdCBjb3VsZCBiZSBoZWxwZnVsIHRvIGxvb2sgYXQgb3RoZXIgaW1wb3J0YW50IHZhcmlhYmxlcyBzdWNoIGFzIGRlcG9zaXQgdHlwZSwgcHJldmlvdXMgY2FuY2VsbGF0aW9uLCBhbmQgYXNzaWduZWQgcm9vbSB0eXBlIHdoaWNoIG1ha2UgZ3Vlc3RzIG1vcmUgbGlrZWx5IHRvIGNhbmNlbC4gVGhleSBjYW4gYWxzbyB1c2UgdGhlIG1vZGVsIHRvIGltcHJvdmUgdGhlaXIgcmVzZXJ2YXRpb24gc3lzdGVtLiBGb3IgZXhhbXBsZSwgaWYgdGhleSBtYWtlIGFsbCBkZXBvc2l0cyBub24tcmVmdW5kYWJsZSB0byBsb3dlciB0aGUgbGlrZWxpaG9vZCBvZiBjYW5jZWxsYXRpb24uDQoNCjkuIEhvdyBtaWdodCB5b3UgZ28gYWJvdXQgcXVlc3Rpb25pbmcgYW5kIGV2YWx1YXRpbmcgdGhlIG1vZGVsIGluIHRlcm1zIG9mIGZhaXJuZXNzPyBBcmUgdGhlcmUgYW55IHF1ZXN0aW9ucyB5b3Ugd291bGQgbGlrZSB0byBhc2sgb2YgdGhlIHBlb3BsZSB3aG8gY29sbGVjdGVkIHRoZSBkYXRhPyANCg0KPiBJIHdvdWxkIGxpa2UgdG8gbGVhcm4gdGhlIHByb3BvcnRpb24gb2YgeW91bmcgYW5kIG9sZCBwZW9wbGUgYW5kIHRoZSBwcm9wb3J0aW9uIG9mIGRpZmZlcmVudCBnZW5kZXIvcmFjZSBncm91cHMgdG8gY2hlY2sgaWYgdGhlIGRhdGEgdW5kZXJyZXByZXNlbnQgYW55IGdyb3VwLiBJIHdvdWxkIGFsc28gbGlrZSB0byBhc2sgd2hhdCB0aGV5IHRoaW5rIGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGluZmx1ZW5jaW5nIGNhbmNlbGxhdGlvbiBiZWNhdXNlIHRoZWlyIHByaW1hcnkga25vd2xlZGdlIG1heSBsZWFkIHRvIGEgYmlhc2VkIGNvbGxlY3Rpb24gb2YgZGF0YS4NCg0KIyMgQmlhcyBhbmQgRmFpcm5lc3MNCg0KUmVhZCBbQ2hhcHRlciAxOiBUaGUgUG93ZXIgQ2hhcHRlcl0oaHR0cHM6Ly9kYXRhLWZlbWluaXNtLm1pdHByZXNzLm1pdC5lZHUvcHViL3ZpOG9ieGg3L3JlbGVhc2UvNCkgb2YgRGF0YSBGZW1pbmlzbSBieSBDYXRoZXJpbmUgRCdJZ25hemlvIGFuZCBMYXVyZW4gS2xlaW4uIFdyaXRlIGEgNC02IHNlbnRlbmNlIHBhcmFncmFwaCByZWZsZWN0aW5nIG9uIHRoaXMgY2hhcHRlci4gQXMgeW91IHJlZmxlY3QsIHlvdSBtaWdodCBjb25zaWRlciByZXNwb25kaW5nIHRvIHRoZXNlIHNwZWNpZmljIHF1ZXN0aW9ucy4gV2Ugd2lsbCBhbHNvIGhhdmUgYSBkaXNjdXNzaW9uIGFib3V0IHRoZXNlIHF1ZXN0aW9ucyBpbiBjbGFzcyBvbiBUaHVyc2RheS4NCg0KKiBBdCB0aGUgZW5kIG9mIHRoZSAiTWF0cml4IG9mIERvbWluYXRpb24iIHNlY3Rpb24sIHRoZXkgZW5jb3VyYWdlIHVzIHRvICJhc2sgdW5jb21mb3J0YWJsZSBxdWVzdGlvbnM6IHdobyBpcyBkb2luZyB0aGUgd29yayBvZiBkYXRhIHNjaWVuY2UgKGFuZCB3aG8gaXMgbm90KT8gV2hvc2UgZ29hbHMgYXJlIHByaW9yaXRpemVkIGluIGRhdGEgc2NpZW5jZSAoYW5kIHdob3NlIGFyZSBub3QpPyBBbmQgd2hvIGJlbmVmaXRzIGZyb20gZGF0YSBzY2llbmNlIChhbmQgd2hvIGlzIGVpdGhlciBvdmVybG9va2VkIG9yIGFjdGl2ZWx5IGhhcm1lZCk/IiBJbiBnZW5lcmFsLCBob3cgd291bGQgeW91IGFuc3dlciB0aGVzZSBxdWVzdGlvbnM/IEFuZCB3aHkgYXJlIHRoZXkgaW1wb3J0YW50PyAgDQoNCj4gSSBiZWxpZXZlIHRoZSBmaWVsZCBvZiBkYXRhIHNjaWVuY2UgaXMgc3RpbGwgcHJlZG9taW5hdGVkIGJ5IG1lbiwgd2hpY2ggaXMgYWxzbyB0aGUgZ3JvdXAgdGhhdCBpcyBwcmlvcml0aXplZC4gQmVjYXVzZSBvZiBoaWdoZXIgc29jaW9lY29ub21pYyBzdGF0dXMsIG1lbiBjb3VsZCBiZSB0aGUgcHJpbWFyeSB0YXJnZXQgb2YgaGlnaC10ZWNoIHByb2R1Y3RzIGFuZCBjYW4gYmVuZWZpdCBmcm9tIHRoZXNlIHRlY2hub2xvZ2llcy4gT24gdGhlIG90aGVyIGhhbmQsIGFsbCBvdGhlciBnZW5kZXJzIGNvdWxkIGJlIG92ZXJsb29rZWQgb3IgYWN0aXZlbHkgaGFybWVkLiBJdCBpcyBpbXBvcnRhbnQgdG8gdGhpbmsgYWJvdXQgdGhlc2UgcXVlc3Rpb25zIGJlY2F1c2Ugd2l0aG91dCB0aGlua2luZyBkZWVwbHkgYWJvdXQgdGhlbSwgcGVvcGxlIG1pZ2h0IG5vdCBldmVuIHJlYWxpemUgdGhlIHByb2JsZW0uIEFuZCByZWFsaXppbmcgYSBwcm9ibGVtIGlzIHRoZSBmaXJzdCBzdGVwIHRvIHNvbHZlIGl0LiBJZiB0aGUgcHJvYmxlbSBpcyBub3Qgbm90aWNlZCBhbmQgY29ycmVjdGVkIGluIHRpbWUsIHRoZSBkYXRhIGNvbGxlY3RlZCBsZWFkcyB0byBhIGJpYXNlZCBwcm9kdWN0IHdoaWNoIHRoZW4gbGVhZHMgdG8gYSBtb3JlIGJpYXNlZCBjb2xsZWN0aW9uIG9mIGRhdGEuIFN1Y2ggYSBjeWNsZSBjYW4gZ28gb24gYW5kIG9uIGFuZCBmdXJ0aGVyIGhhcm0gdGhlIG1pbm9yaXRpemVkIGdyb3Vwcy4NCg0KKiBDYW4geW91IHRoaW5rIG9mIGFueSBleGFtcGxlcyBvZiBtaXNzaW5nIGRhdGFzZXRzLCBsaWtlIHRob3NlIGRlc2NyaWJlZCBpbiB0aGUgIkRhdGEgU2NpZW5jZSBmb3IgV2hvbT8iIHNlY3Rpb24/IE9yIHdhcyB0aGVyZSBhbiBleGFtcGxlIHRoZXJlIHRoYXQgc3VycHJpc2VkIHlvdT8gIA0KDQo+IEkgd2FzIHN1cnByaXNlZCB0aGF0IE1vYmlsaXR5IGZvciBvbGRlciBhZHVsdHMgd2l0aCBwaHlzaWNhbCBkaXNhYmlsaXRpZXMgb3IgY29nbml0aXZlIGltcGFpcm1lbnRzIGlzIG1pc3NpbmcuIFBoeXNpY2FsIGRpc2FiaWxpdGllcyBhbmQgY29nbml0aXZlIGltcGFpcm1lbnRzLCBJIGJlbGlldmUsIGFyZSBzeW1wdG9tcyBhIGxhcmdlIGdyb3VwIG9mIGVsZGVybHkgcGVvcGxlIGV4cGVyaWVuY2VzLiBXaXRoIGluY3JlYXNpbmcgYXR0ZW50aW9uIHRvIHB1YmxpYyBoZWFsdGgsIGl0IGlzIHN1cnByaXNpbmcgdGhhdCB0aGVyZSBpcyBubyBkYXRhc2V0IGNvdmVyaW5nIHRoaXMgYXJlYS4gVGhpcyBjb3VsZCBhbHNvIGluZGljYXRlIHRoZSBvbGRlciBhZHVsdHMgYXJlIGFuIG92ZXJsb29rZWQgYW5kIG1pbm9yaXRpemVkIGdyb3VwLCB3aGljaCBtYWtlcyBubyBzZW5zZSBiZWNhdXNlIHRoZXkgYXJlIHRoZSBncm91cCBtb3JlIGxpa2VseSB0byBleHBlcmllbmNlIHRoZSBtb3N0IGhlYWx0aC1yZWxhdGVkIGlzc3Vlcy4NCg0KKiBIb3cgZGlkIHRoZSBleGFtcGxlcyBpbiB0aGUgIkRhdGEgU2NpZW5jZSB3aXRoIFdob3NlIEludGVyZXN0cyBhbmQgR29hbHM/IiBzZWN0aW9uIG1ha2UgeW91IGZlZWw/IFdoYXQgcmVzcG9uc2liaWxpdHkgZG8gY29tcGFuaWVzIGhhdmUgdG8gcHJldmVudCB0aGVzZSB0aGluZ3MgZnJvbSBvY2N1cnJpbmc/IFdobyBpcyB0byBibGFtZT8NCg0KPiBJIGZlZWwgc2FkIGJlY2F1c2UgSSBhbHdheXMgc2VlIGRhdGEgYXMgc29tZXRoaW5nIG1vc3RseSBvYmplY3RpdmUgYW5kIGFzIGEgdG9vbCB0byBwcm92aWRlIGV2aWRlbmNlLWJhc2VkIGV4cGxhbmF0aW9ucy9jb25jbHVzaW9ucy4gVGhlc2UgZXhhbXBsZXMgc2hvdyB0aGF0IEkgYW0gd3JvbmcgYW5kIEkgYW0gdXBzZXQgdG8gc2VlIHRoYXQgZGF0YSBpcyBub3QgYnJpbmdpbmcgdGhlIGdvb2QgYXMgaXQgc3VwcG9zZXMgdG8gYmUuIENvbXBhbmllcyBzaG91bGQgYmUgZXh0cmEgY2FyZWZ1bCBhYm91dCB0aGUgc291cmNlIG9mIHRoZWlyIGRhdGEsIGhvdyB0aGVpciBkYXRhIHdhcyBjb2xsZWN0ZWQsIGFuZCB3aGV0aGVyIHRoZWlyIGRhdGEgdW5kZXIgb3Igb3ZlcnJlcHJlc2VudCBhbnkgZ3JvdXBzLiBBbnlvbmUgaW4gdGhlIHByb2Nlc3MgY291bGQgcG90ZW50aWFsbHkgZG8gc29tZXRoaW5nIGluY29ycmVjdGx5LCBidXQgbW9yZSBpbXBvcnRhbnQgaXMgdG8gbGV0IHBlb3BsZSByZWFsaXplIHBvdGVudGlhbCBwcm9ibGVtcyBhc3NvY2lhdGVkIHdpdGggdGhlIHVzYWdlIG9mIGRhdGEuDQo=